<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button>按钮1~</button>
  </body>
</html>
<script src="https://cdn.bootcss.com/rxjs/6.0.0-alpha.3/Rx.min.js"></script>
<script>
  //注册事件监听
  // var button = document.querySelector('button');
  // button.addEventListener('click',()=>console.log('点击'))

  //rxjs
  // var button = document.querySelector('button');
  // Rx.Observable.fromEvent(button,'click')
  //              .subscribe(()=>console.log('rxjs点击'))

  //纯净性(Purity)
  //使得RxJS强大的正是它使用纯函数来生产值得能力。这意味着你得代码更不容易出错。
  //通产你会创建一个非纯函数，在这个函数之外也使用了共享变量的代码，这将使得你的应用状态一团糟
  //var count = 0;
  //var button = document.querySelector('button');
  //button.addEventListener('click',()=>console.log(`${++count}`))
  //使用RxJS的话，你会将应用状态隔离出来
  // var button = document.querySelector('button');
  // Rx.Observable.fromEvent(button,'click')
  //              .scan(count=>count+1,0)
  //              .subscribe(count=>console.log(`${count}`))

  //流动性
  //RxJS提供了一整套操作符来帮助你控制事件如何流经observables。
  //下面的代码展示的是如何控制一秒钟内最多点击一次，先来看使用普通的JavaScript:
  // var count = 0;
  // var rate = 1000;
  // var lastClick = Date.now() - rate;
  // var button = document.querySelector('button');
  // button.addEventListener('click',()=>{
  //     console.log('clicked')
  //     if(Date.now() - lastClick >= rate){
  //         console.log(`${++count}`);
  //         lastClick = Date.now();
  //     }
  // })
  //RxJS
  // var button = document.querySelector('button');
  // Rx.Observable.fromEvent(button,'click')
  //              .throttleTime(1000)
  //              .scan(count => count + 1,0)
  //              .subscribe(count=>console.log(`${count}`))

  //值(Value)
  //对于流经observables的值，你可以对其进行转换
  //下面的代码展示的是如何累加每次点击的鼠标x坐标，先来看使用普通的Javascript
  // var count = 0;
  // var rate = 1000;
  // var lastClick = Date.now() - rate;
  // var button = document.querySelector('button');
  // button.addEventListener('click',(event)=>{
  //     if(Date.now() - lastClick >= rate){
  //         count += event.clientX;
  //         console.log(count);
  //         lastClick = Date.now();
  //     }
  // })
  //使用RxJs
//   var button = document.querySelector("button");
//   Rx.Observable.fromEvent(button, 'click')
//     .throttleTime(1000)
//     .map((event) => event.clientX)
//     .scan((count, clientX) => count + clientX, 0)
//     .subscribe(count=>console.log(count));

   //Observable(可观察对象)
   //Observable是多个值的惰性推送集合。
   //           单个值                多个值
   // 拉去      Function              Iterator
   // 推送      Promise               Observable
   
   //当订阅下面代码中的Observable的时候会立即（同步地）推送值1，2，3然后1秒后会推送值4，再然后是完成流
  //  var observable = Rx.Observable.create(function(observer){
  //      observer.next(1);
  //      observer.next(2);
  //      observer.next(3);
  //      setTimeout(()=>{
  //          observer.next(4);
  //          observer.complete();
  //      },1000);
  //  })
  //  console.log('订阅前--------------')
  //  observable.subscribe({
  //      next: x=> console.log('got value' + x),
  //      error: err=>console.error('something wrong occurred:' + err),
  //      complete: ()=>console.log('完成')
  //  })
  //  console.log('订阅后---------------')

//拉取(Pull) vs 推送(Push)
//拉取和推送是两种不同的协议，用来描述数据生产者(Producer)如何与数据消费者(Consumer)进行通信
//什么是拉取？ -在拉取体系中，由消费者来决定合适从生产者那里接受数据。生产者本身不知道数据是何时交付到消费者手中的。
//每个JavaScript函数都是拉取体系。函数是数据的生产者，调用该函数的代码通过从函数调用中取出一个单个返回值来对该函数进行消费。
//ES2015引入了generator函数和iterators(function*),这是另外一种类型的拉取体系。调用iterator.next()的代码消费者，他从iterator（生产者）那去除多个值

//                     生产者                                      消费者
//  拉取          被动的：当被请求时产生数据。                 主动的：决定何时请求数据
//  推送          主动的：按自己的节奏产生数据。               被动的:对收到的数据做出反应

//什么时推送?  ----在推送体系中，由生产者来决定何时把数据发送给消费者。消费者本身不知道何时会接收到数据。
//在当今的JavaScript世界中,Promises时最常见的推送体系类型。Promise（生产者）将一个解析过的值传递给已注册的回调函数（消费者），但不同于函数的是，由promise来决定何时把
//把值推送给回调函数。
//RxJS引入Observables,一个新的JavaScript推送体系。Observable是多个值的生产者，并将值推送给观察者（消费者）。
//Function是惰性的评估运算，调用时会同步地返回一个单一值。
//Generator是惰性地评估运算，调用时会同步地返回零到（有可能的）无限多个值
//Promise是最终可能（或不可能）返回单个值的运算
//Observable是惰性的评估运算，它可以从它被调用的时刻起同步或异步地返回零到（有可能的）无线多个值

//Observables作为函数的泛化
//与流行的说法正好相反，Observables既不像EventEmitters，也不像多个值的Promises.在某些情况下，即
//当使用RxJs的Subjects进行多播时，Observables的行为可能会比较像EventEmitters，但通常情况下Observables的
//行为并不像EventEmitters.
//Observables像是没有参数，当可以泛化为多个值的函数

// function foo(){
//   console.log('hello');
//   return 42;
// }
// var x = foo.call();
// console.log(x);
// var y = foo.call();
// console.log(y);

//rxjs
// var foo = Rx.Observable.create(function(observer){
//   console.log('hello');
//   observer.next(42);
// })

// foo.subscribe(function(x){
//   console.log(x);
// })

// foo.subscribe(function(y){
//   console.log(y)
// })

//先核实因为函数和Observables都是惰性运算。如果你不调用函数，console.log('hello')就不会执行。Observables也是如此，
//如果你不调用它(使用subscribe)，console.log('hello')也不会执行。此外，调用或订阅是独立操作：两个函数调用会触发两个
//单独的副作用，两个Observable订阅同样也是触发两个单独的副作用。EventEmitters共享副作用并且无论是否存在订阅者都会尽早
//执行，Observables与之相反，不会共享副作用并且是延迟执行。
// var foo = Rx.Observable.create(function(observer){
//   console.log('hello');
//   observer.next(42);
// })
// console.log('before');
// foo.subscribe(function(x){
//   console.log(x);
// })
// console.log('after')
//foo订阅完全是同步的，就像函数一样
//Observables传递值可以是同步的，也可以是一异步的。
//那么Observable和函数的区别是什么呢？Observable可以随着时间的推移返回多个值，这是函数所做不到的。
// var foo = Rx.Observable.create(function(observer){
//   console.log('hello');
//   observer.next(42);
//   observer.next(100);
//   observer.next(200);
// });
// console.log('before');
// foo.subscribe(function(x){
//   console.log(x)
// })
// console.log('after')


//observable刨析
//Observables时使用Rx.Observable.create或创建操作符创建的，并使用观察者来订阅它，然后执行它并发送next/error/complete通知给观察者，
//而且执行可能会被清理。
//Observable的核心关注点：
//创建Observables
//订阅Observables
//执行Observables
//清理Observables

//创建Observables
//Rx.Observable.create是Observable构造函数的别名，它接受一个参数:subscribe函数
// var observable = Rx.Observable.create(function(observable){
//   var id = setInterval(()=>{
//     observable.next('hi')
//   },100)
// })
//Observables可以使用create来创建，但通常我们使用所谓的创建操作符，像of,from,interval等等

//订阅Observables
//obserable.subscribe(x=>console.log(x));
//observable.subscribe和Observable.create(function subscribe(observable){...})中的subscribe
//有着同样的名字，这并不是一个巧合。在库中，它们是不同的，但从实际出发，你可以认为在概念上他们是等同的。
//这表明subscribe调用在同一Observable的多个观察者之间是不共享的。当使用一个观察者调用Observable.subscribe时，
//Observable.create(function subscribe(observer){...})中的subscribe函数只服务于给定的观察者。对observable.
//subscribe的每次调用都会触发正对给定观察者的独立设置。
//


//执行Observables
//Observable.create(function subscribe(observer){...})中的...的代码表示Observabel执行，它是惰性运算，只有在每个
//观察者订阅后才会执行。随着时间推移，执行会以同步或异步的方式产生多个值
//Observable执行可以传递三种类型的值：
//Next通知：发送一个值，比如数字、字符串、对象、等等。
//Error通知：发送一个JavaScript错误或异常。
//Complete通知：不再发送任何值
//Next通知是最重要，也是最常见的类型：他们表示传递给观察者的实际数据。Error和Complete通知可能只会在Observable
//执行期间发生一次，并且只会执行其中的一个。

//Observable执行中，可能会发送零个到无穷多个Next通知。如果发送的是Error或Complete通知的话，那么之后不会再发送
//任何通知了
// var observable = Rx.Observable.create(function subscribe(observer){
//    observer.next(1);
//    observer.next(2);
//    observer.next(3);
//    observer.complete();
// })
//Observable严格遵守自身的规约，所以下面的代码不会发送next通知4
// var observable = Rx.Observable.create(function subscribe(observer){
//   observer.next(1);
//   observer.next(2);
//   observer.next(3);
//   observer.complete();
//   observer.next(4);//因为违反规约，所以不会发送
// })

//在subscribe中用try/catch代码块来包裹任意代码是个不错的主意，如果捕获到异常的话，会发送Error通知：
// var obserable = Rx.Observable.create(function subscribe(observer){
//   try{
//     observer.next(1);
//     observer.next(2);
//     observer.next(3);
//     observer.complete();
//   }catch(err){
//     observer.error(err);//如果捕获到异常会发送一个错误
//   }
// })

//执行Observable清理
//因为Observable执行可能会是无限的，并且观察者通常希望能在有限的时间内终止执行，所以我们需要一个API来取消
//执行。因为每个执行都是其对应观察者专属的，一旦观察者观察者完成接收值，它必须要一种方法来停止执行，以避免
//浪费计算能力或内存资源。
//当调用了observable.subscribe，观察者会被附加到新创建的Observable执行中。这个调用还返回一个对象,即Subscription(订阅):
//var subscription = observable.subscribe(x=>console.log(x))
//Subscription表示进行中的执行，它有最小化的API以允许你取消执行。使用subscription.unsubscribe()你可以取消进行中的执行：
// var obserable = Rx.Observable.from([10,20,30]);
// var subscription = obserable.subscribe(x=>console.log(x));
// //稍后
// subsciption.unsubscribe();
//当你订阅了Observable，你会得到一个Subscription，它表示进行中的执行。只要调用unsubscribe()方法就可以取消执行。
//当我们使用create()方法创建Observable时，Observable必须定义如何清理执行的资源。你可以通过在function subscribe()返回一个
//自定义的unscribe函数
//例子：清理使用了setInterval的interval执行集合
// var obserable = Rx.Obserable.create(function subscribe(observer){
//   //追踪Interval
//   var intervalID = setInterval(()=>{
//     observer.next('hi')
//   },1000);
//   //提供取消和清理interval资源的方法
//   return function unsubscribe(){
//     clearInterval(intervalID)
//   }
// })
// var unsubscribe = subscribe({
//   next: (x)=>console.log(x)
// })
// //稍后
// unsubscribe();//清理资源

//Observer(观察者)
//什么是观察者？ -观察者是由Observable发送的值的消费者。观察者只是一组回调函数的集合，每个回调函数对应
//一种Observable发送的通知类型：next、error和complete。
// var observer = {
//   next: x=>console.log('Observer got a next value' + x),
//   error: error=>console.error('Observer got an error' + err),
//   complete: ()=>console.log('Observer got a complete notification')
// };
//演示用观察者,需要把它提供给Observable的subscibe方法：
// observable.subscribe(observer);
//观察者只是有三个回调函数的对象，每个回调函数对应一种Observable发送的通知类型。
//RxJS中的观察者也可能是部分的。如果你没有提供某个回调函数，Observable的执行也会正常运行，只是某些
//通知类型会被忽略，因为观察者中没有相应的回调函数。
// var observer = {
//   next: x=>console.log('Observer got a next value'+ x),
//   error: err=>console.error('Observer got an error:' + err)
// }
//当订阅Observable时，你可能只提供一个回调函数作为参数，而并没有将其附加到观察者对象上
// observer.subscribe(x=>console.log('Observer got a next value' + x))
//在Observable.subscribe内部，它会创建一个观察者对象并使用第一个回调函数参数作为next的处理方法。
//三种类型的回调函数都可以直接作为参数来提供：
// observer.subscribe(
//   x=>console.log('Observer got a next value' + x),
//   err => console.error('Observer got a an error'+ error),
//   ()=>console.log('Observer got a complete notification')
// )

//Subscription(订阅)
//什么是subscription? --Subscription是表示可清理资源的对象，通常是observable的执行。Subscription有一个重要的方法，即unsubscribe,
//它不需要任何参数，只是用来清理有Subscription占用的资源。
// var observable = Rx.Observable.interval(1000);
// var subscription = observable.subscribe(x=>console.log(x));
// subscribe.unsubscribe();
//Subscription基本上只有一个unsubscribe()函数，这个函数用来释放资源或去取消Observable执行
//Subscription还可以合在一起，这样一个Subscription调用unsubscribe()方法，可能会有多个Subscription取消订阅。
//你可以通过一个Subscription添加到另一个上面来做这件事
// var observable1 = Rx.Observable1.interval(400);
// var observable2 = Rx.Observable2.interval(300);
// var subscription = observable1.subscribe(x => conosle.log('first:'+x));
// var childSubscription = observable2.subscribe(x=>console.log('second:'+x));
// subscription.add(childSubscription);
// setTimeout(()=>{
//   //subscription和childSubscription都会取消订阅
//   subscription.unsubscribe();
// })
//Subscription还有一个remove(otherSubscription)方法，用来撤销一个已添加的子Subscription.

//Subject(主体)
//什么是Subject? ---RxJS Subject是一种特殊类型的Observable，它允许将值多播给多个观察者，所以Subject是多播的，
//而普通的Observables是单播的（每个已订阅的观察者都拥有Observable的独立执行）.
//Subject像是Observable,但是可以多播给多个观察者。Subject还像是EventEmitters，维护这多个监听器的注册表。
//每个Subject都是Observable。--对于Subject,你可以提供一个观察者并使用subscribe方法，就可以开始正常接受值。从观察者的角度而言,
//它无法判断Observable执行是来之普通的Observable还是Subject.
//在Subject的内部,subscribe不会调用发送值得新执行。他只是将规定得观察者注册到观察者列表中，类似于其他库或语言中的addListener的工作方式.
//每个Subject都是观察者。---Subject是一个有如下方法的对象：next(v),error(e)和complete()。要给Subject提供新值，只要调用next(theValue)，
//它会将值多播给已注册监听该Subject的观察者们。
// var subject = new Rx.Subject();
// subject.subscribe({
//   next:(v)=>console.log('observerA' + v)
// });
// subject.subscribe({
//   next:(v)=>console.log('observerB' + v)
// })
// subject.next(1);
// subject.next(2);
//因为Subject是观察者，这也就在意味着你可以把Subject作为参数传给任何Observable的subscribe方法
// var subject = new Rx.Subject();

// subject.subscribe({
//   next: (v)=>console.log('observerA' + v)
// })
// subject.subscribe({
//   next:(v)=>console.log('observerB' + v)
// })
// var observable = Rx.Observable.from([1,2,3]);
// observable.subscribe(subject); //你可以提供一个Subject进行订阅

//多播的Observables
//多播Observable通过Subject来发送通知，这个Subject可能有多个订阅者，然而普通的单播Observable只发送通知给单个观察者。
//多播Observable在底层是通过使用Subject使得多个观察者可以看见同一个Observable执行。
//在底层，这就是multicast操作符的工作原理：观察者订阅一个基础的Subject，然后Subejct订阅源Observable.
// var source = Rx.Observable.from([1,2,3]);
// var subject = new Rx.Subject();
// var multicasted = source.multicast(subject);
// //在底层使用了`subject.subscribe({...})`:
// multicasted.subscribe({
//   next:(v)=>console.log('observerA:' + v)
// });
// multicasted.subscribe({
//   next: (v)=>console.log('obsserverB:' +v)
// })
// //在底层使用了`source.subscribe(subject)`:
// multicasted.connect();
//multicast操作符返回一个Observable，它看起来和普通的Observable没什么区别，但当订阅时就像是Subject.
//multicast返回的是ConnectableObservable,它只是一个有connect()方法的Observable.
//connect()方法十分重要，它决定了何时启动共享的Observable执行。因为connect()方法在底层执行了source.subscribe(subject),
//所以它返回的是Subscription,你可以订阅取消共享的Observable执行。


//引用计数
//手动调用connect()并处理Subscription通常太笨重。通常，当第一个观察者到达时我们想要自动地连接，而当最后一个观察者取消订阅时
//我们想要自动地取消共享执行
//1.第一个观察者订阅了多播Observable
//2.多播Observable已连接
//3.next值0发送给第一个观察者
//4.第二个观察者订阅了多播Observable
//5.next值1发送给第一个观察者
//6.next值1发送给第二个观察者
//7.第一个观察者取消了多播Observable地订阅
//8.next值2发送给第二个观察者
//9.第二个观察者取消了多播Observable的订阅
//要实现这一点，需要显示地调用connect()
// var source = Rx.Observable.interval(500);
// var subject = new Rx.Subject();
// var multicasted = source.multicast(subject);
// var subscription1,subsciption2,subscriptionConnect;

// subscription1 = multicasted.subject({
//   next:(v)=>console.log('observerA:' + v)
// })
//这里我们应该调用`connect()`,因为`multicasted`的第一个
//订阅者关心消费值
// subscriptionConnect = multicasted.connect();

// setTimeout(()=>{
//   subscription2 = multicasted.subscribe({
//     next:(v)=>console.log('observerB:' + v)
//   })
// },600);

// setTimeout(()=>{
//   subscription1.unsubscribe()
// },1200)
//这里我们应该取消共享的Observable执行的订阅
//因为此后`multicasted`将不再有订阅者
// setTimeout(()=>{
//   subscription2.unsubscribe();
//   subscriptionConnect.unsubscribe();//用于共享的Observable执行
// },200)
//如果不想显式调用connect(),我们可以使用ConnectableObservable的refCount()方法（引用计数）
//这个方法返回Observable，这个Observable会追踪有多少个订阅者。当订阅者的数量从0变成1，它会调用
//connect()以开启共享的执行。当订阅者数量从1变成0时，它会完全取消订阅，停止进一步的执行。

//refCount的作用是，当有第一个订阅者时，多播Observable会自动地执行，而当最后一个订阅者离开时，多播Observable会自动地停止执行。
// var source = Rx.Observable.interval(500);
// var subject = new Rx.Subject();
// var refCounted = source.multicast(subject).refCount();
// var subscription1,subscription2,subscriptionConnect;

//这里其实调用了`connect()`,
//因为`refCounted`有了第一个订阅者
// console.log('observerA subscribed');
// subscription1 = refCounted.subscribe({
//   next: (v)=>console.log('observerA:'+v)
// });

// setTimeout(()=>{
//   console.log('observerB subscribed');
//   subsciption2 = refCounted.subscribe({
//      next:(v)=>console.log('observerB' + v)   
//   })
// },600);

// setTimeout(()=>{
//   console.log('observerA unsubscribed');
//   subscription1.unsubscribe()
// },1200);

//这里共享的Observable执行停止
//因为此后`refCounted`将不再有订阅者
// setTimeout(()=>{
//   console.log('observerB unsubscribed');
//   subscription2.unsubscribe();
// },2000)
//refCount()只存在于ConnectableObservable，它返回的是Observable,而不是另一个ConnectableObservable


//scheduler（调度器）
//什么时调度器？  --调度器控制着何时启动subscription和何时发送通知。它由三部分组成
//1.调度器是一种数据结构。它知道如何根据优先级或其他标准来存储任务和将任务进行排序
//2.调度器是执行上下文。它表示在何时何地执行任务
//3.调度器有一个（虚拟的）时钟。调度器功能通过它的getter方法now()提供了时间的概念。在具体调度器上安排的任务将严格
//遵循该时钟所表示的时间

//调度器可以让你规定Observable在什么样的执行上下文中发送通知给它的观察者。
//采用普通的Observable，它同步地发出值1，2，3，并使用操作符observeOn来指定async调度器发送这些值
// var obserable = Rx.Observable.create(function(observer){
//   observer.next(1);
//   observer.next(2);
//   observer.next(3);
//   observer.complete();
// })
// .ObserveOn(Rx.Scheduler.async);

// console.log('just before subscribe');
// obserable.subscribe({
//   next: x=>console.log('got value' + x),
//   error: err=>console.error('something wrong occurred:' + err),
//   complete: ()=>console.log('done')
// });
// console.log('just after subscribe')
//注意通知got value...在just after subscribe之后才发送，这与我们到目前为止所见地默认行为是不一样的。这是因为observeOn(Rx.Scheduler.async)
//在Observable.create和最终的观察者之间引入了一个代理观察者。
// var obserable = Rx.Observable.create(function(proxyObserver){
//   proxyObserver.next(1);
//   proxyObserver.next(2);
//   proxyObserver.next(3);
//   proxyObserver.complete();
// }).ObserveOn(Rx.Scheduler.async);

// var finalObserver = {
//   next: x=>console.log('got value' + x),
//   error: err=>console.error('something wrong occurred:' + err),
//   complete: ()=>console.log('done')
// }

// console.log('just before subscribe');
// obserable.subscribe(finalObserver);
// console.log('just after subscribe');

//调度器类型
//async调度器是RxJS提供的内置调度器中的一个。可以通过使用Scheduler对象的静态属性创建并返回其中的每种类型的调度器。
//            调度器                                            目的
//             null                           不传递任何调度器的话，会以同步递归的方式发送通知。用于定时操作或尾递归操作。
//         Rx.Scheduler.queue                 当前事件帧中的队列调度（蹦床调度器）。用于迭代操作。
//         Rx.Scheduler.asap                  微任务的队列调度，它使用可用的最快速的传输机制，比如Node.js的process.nextTick()或Web Worker的MessageChannel或setTimeout或其他。用于异步转换。
//         Rx.Scheduler.async                 使用setInterval的调度。用于基于时间的操作符

//在RxJS代码中已经使用过调度器了，只是没有明确地指明要使用的调度器类型。这是因为所有的Observable操作符处理并发性都有可选的调度器。
//如果没有提供调度器的话，RxJS会通过使用最小并发原则选择一个默认调度器。这意味着引入满足操作符需要的最下并发量的调度器会被选择。例如
//对于返回有限和少量消息的observable的操作符,RxJS不适用调度器，即null或undefined。对于返回潜在大量的或无限数量的消息
//RxJS使用最少的并发调度器，如果处于性能考虑，想要引入并发，那么可以选择不同的调度器，可以使用那些采用调度器的操作符方法，例如from([10,20,30],Rx.Scheduler.async)

//静态创建操作符通常可以接收调度器作为参数。from(array,scheduler)可以让你指定调度器，当发送从array转换的每个通知的时候使用。调度器通常作为操作符的最后一个参数。
</script>
